package com.study.chunguard.common.service;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.study.chunguard.common.exception.IdempotencyException;
import com.study.chunguard.common.exception.LockException;
import com.study.chunguard.common.lock.DistributedLock;
import com.study.chunguard.common.lock.LockCondition;
import com.study.chunguard.common.utils.QueryWrapperBuilder;

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * service接口父类
 *
 * @author zhaowenhao
 * @since 2022-12-10
 */
@SuppressWarnings("unchecked")
public interface ISuperService<T> extends IService<T> {

    /**
     * 使用分布式锁确保保存操作的幂等性。该方法接受一个或多个 LockCondition 对象，对每个条件进行唯一性检查。
     * 所有的条件都必须满足（即对应的记录在数据库中不存在）才会保存实体，否则抛出 IdempotencyException 异常。
     *
     * @param entity         需要保存的实体对象
     * @param locker         分布式锁对象
     * @param lockConditions 一个或多个锁条件，每个条件对应一个唯一性检查
     * @return 如果实体保存成功则返回 true，否则抛出异常
     * @throws LockException        如果无法获取锁或锁等待超时则抛出此异常
     * @throws IdempotencyException 如果任何唯一性检查失败则抛出此异常
     */
    boolean saveIdempotency(T entity, DistributedLock locker, LockCondition<T>... lockConditions);

    /**
     * 以幂等方式根据实体的ID进行更新。此方法使用分布式锁和锁定条件，以确保操作的幂等性和线程安全性。
     *
     * @param entity         需要更新的实体。
     * @param locker         分布式锁。
     * @param lockConditions 在更新之前需要检查的锁定条件。
     *                       每个条件包括一个锁定键，一个用于检查现有数据的查询包装器，
     *                       以及在存在重复数据时抛出异常的消息。
     * @return 如果更新操作成功，则返回true；否则返回false。
     * @throws LockException        如果分布式锁为null或无法获取。
     * @throws IdempotencyException 如果存在重复数据或在更新操作中发生其他异常。
     */
    boolean updateByIdIdempotency(T entity, DistributedLock locker, LockCondition<T>... lockConditions);

    /**
     * 这个方法提供了一个并发安全的方式来保存或更新给定的实体。
     * 如果实体已经存在，那么它会被更新，否则，它将会被保存。
     * <p>
     * 这个方法使用了分布式锁来确保并发安全，并且在执行前会检查是否存在满足条件的记录，从而确保操作的幂等性。
     * 锁和条件检查的行为可以通过 {@link LockCondition} 对象进行配置。
     *
     * @param entity         要保存或更新的实体
     * @param lock           分布式锁对象，用于确保并发安全
     * @param lockConditions 锁条件对象数组，定义了锁和条件检查的行为
     * @return 如果操作成功，则返回 true，否则返回 false
     * @throws IdempotencyException 如果存在满足条件的记录，或者找不到有效的 {@link TableId} 字段
     */
    boolean saveOrUpdateIdempotency(T entity, DistributedLock lock, LockCondition<T>... lockConditions);

    /**
     * 根据查询对象获取对象
     *
     * @param query        查询对象
     * @param orQueryArray OR嵌套查询对象数组，如果有的话
     * @param <Q>          查询对象类型
     * @return 结果对象
     */
    default <Q> T get(Q query, Q... orQueryArray) {
        return getOne(getWrapper(query, orQueryArray), Boolean.FALSE);
    }

    /**
     * 根据查询对象获取列表
     *
     * @param <Q>          查询对象类型
     * @param query        查询对象
     * @param orQueryArray OR嵌套查询对象数组，如果有的话
     * @return 结果列表
     */
    default <Q> List<T> list(Q query, Q... orQueryArray) {
        return list(getWrapper(query, orQueryArray));
    }

    /**
     * 根据查询对象获取列表,并执行分组,返回Map,适用于确定分组后只有一条数据的情况,否则保留最后一条
     *
     * @param keyMapper    生成键的映射函数
     * @param query        查询对象
     * @param orQueryArray OR嵌套查询对象数组，如果有的话
     * @param <Q>          查询对象类型
     * @param <K>          返回的Map中的key的类型
     * @return 结果Map
     */
    default <Q, K> Map<K, T> toMap(Function<T, K> keyMapper, Q query, Q... orQueryArray) {
        List<T> list = list(getWrapper(query, orQueryArray));
        return list.stream()
                .collect(Collectors.toMap(keyMapper, Function.identity(), (oldValue, newValue) -> newValue));
    }

    /**
     * 根据查询对象获取列表并执行分组，返回Map，每个键都有一个对象列表
     *
     * @param keyMapper    生成键的映射函数
     * @param query        查询对象
     * @param orQueryArray OR嵌套查询对象数组，如果有的话
     * @param <Q>          查询对象类型
     * @param <K>          返回的Map中的key的类型
     * @return 结果Map，其值是对象列表
     */
    default <Q, K> Map<K, List<T>> toGroup(Function<T, K> keyMapper, Q query, Q... orQueryArray) {
        List<T> list = list(getWrapper(query, orQueryArray));
        return list.stream()
                .collect(Collectors.groupingBy(keyMapper));
    }

    /**
     * 计数查询
     *
     * @param query        查询对象
     * @param orQueryArray OR嵌套查询对象数组，如果有的话
     * @param <Q>          查询对象类型
     * @return 计数结果
     */
    default <Q> long count(Q query, Q... orQueryArray) {
        return count(getWrapper(query, orQueryArray));
    }

    /**
     * 分页查询
     *
     * @param page         分页对象
     * @param query        查询对象
     * @param orQueryArray OR嵌套查询对象数组，如果有的话
     * @param <Q>          查询对象类型
     * @param <E>          分页对象类型
     * @return 分页对象
     */
    default <Q, E extends IPage<T>> E page(E page, Q query, Q... orQueryArray) {
        return page(page, getWrapper(query, orQueryArray));
    }

    /**
     * 根据查询对象修改记录
     *
     * @param entity       实体对象
     * @param query        查询对象
     * @param orQueryArray OR嵌套查询对象数组，如果有的话
     * @param <Q>          查询对象类型
     * @return 是否成功
     */
    default <Q> boolean update(T entity, Q query, Q... orQueryArray) {
        return update(entity, getWrapper(query, orQueryArray));
    }

    /**
     * 根据查询对象删除记录
     *
     * @param query        查询对象
     * @param orQueryArray OR嵌套查询对象数组，如果有的话
     * @param <Q>          查询对象类型
     * @return 是否成功
     */
    default <Q> boolean remove(Q query, Q... orQueryArray) {
        return remove(getWrapper(query, orQueryArray));
    }

    /**
     * 内部方法根据查询对象获取查询条件
     *
     * @param query        查询对象
     * @param orQueryArray OR嵌套查询对象数组，如果有的话
     * @return 查询条件
     */
    default <Q> QueryWrapper<T> getWrapper(Q query, Q... orQueryArray) {
        return QueryWrapperBuilder.build(query, orQueryArray);
    }
}
